import os
import time
import cPickle as pickle

class MSFileInfo:
    def __init__(self):
        self.datanum    = 0
        self.revision   = 0
        self.msobject   = None

class MSFile:
    def __init__(self, dirname, filename):
        self.dirname    = dirname
        self.filename   = filename

        self.lockname   = dirname + filename + ".lock"
        self.infoname   = dirname + filename + ".info"
        self.dataname   = dirname + filename + ".data"

        self.arcname    = dirname + filename + ".arc"
        self.arclock    = dirname + filename + ".arclock"

        self.tempname   = dirname + filename + ".temp"

        self.info       = MSFileInfo()
        self.data       = [] #TBD init to None

    def load(self):
        self.lock()

        lastinfo = self.getInfo()
        if lastinfo: self.info = lastinfo
        lastdata = self.getData()
        if lastdata: self.data = lastdata

        self.unlock()

    def save(self):
        while True:
            datatemp = self.getAvailableName(self.tempname)
            data = open(datatemp, 'wb')
            pickle.dump(self.data, data)
            data.close()

            self.lock()
            lastinfo = self.getInfo()
            if lastinfo and lastinfo.revision != self.info.revision:
                lastdata = self.getData()
                self.unlock()
                os.unlink(datatemp)

                self.data = self.data + lastdata
                self.info.revision = lastinfo.revision
                self.data.sort()
                continue

            self.info.datanum = len(self.data)
            self.info.revision += 1
            info = open(self.infoname, 'wb')
            pickle.dump(self.info, info)
            info.close()

            os.rename(datatemp, self.dataname)
            self.unlock()
            break

    ## data helpers ##
    def append(self, chunk): self.data.append(chunk)


    ## helpers ##
    def lock(self):
        sleepnum = 0
        while os.path.exists(self.lockname):
            sleepnum += 1
            time.sleep(sleepnum)
        lock = open(self.lockname, 'w')
        lock.close()
        return sleepnum == 0

    def unlock(self):
        if not os.path.exists(self.lockname): raise RuntimeError('cant unlock unlocked file')
        os.unlink(self.lockname)

    def destroy(self):
        self.lock()
        if os.path.exists(self.infoname): os.unlink(self.infoname)
        if os.path.exists(self.dataname): os.unlink(self.dataname)
        self.unlock()

    def getAvailableName(self, name):
        postfix     = 0
        available   = name
        while os.path.exists(available):
            available = name + '.' + str(postfix)
            postfix += 1
        return available

    def getInfo(self):
        if os.path.exists(self.infoname):
            info = open(self.infoname, 'rb')
            data = pickle.load(info)
            info.close()
            return data
        return None

    def getData(self):
        if os.path.exists(self.dataname):
            data = open(self.dataname, 'rb')
            evts = pickle.load(data)
            data.close()
            return evts
        return None









if __name__ == "__main__":
    import sys
    if len(sys.argv) != 2:
        print "Usage: %s <storage_root>" % (sys.argv[0])
        sys.exit(1)
    g_root = sys.argv[1]
    
    def case1():
        f1 = MSFile(g_root, 'case1')
        f1.destroy()
        f1.save()
        f1.load()
        if f1.info.revision != 1: raise RuntimeError('case1: 1 but %s' % (str(f1.info.revision)))
        f1.save()
        f1.load()
        if f1.info.revision != 2: raise RuntimeError('case1: 2')

        f1 = MSFile(g_root, 'case1')
        f1.load()
        if f1.info.revision != 2: raise RuntimeError('case2: 2')
        f1.save()
        if f1.info.revision != 3: raise RuntimeError('case2: 2')

        f1 = MSFile(g_root, 'case1')
        f1.load()
        if f1.info.revision != 3: raise RuntimeError('case3: 1')
        if f1.info.datanum != 0: raise RuntimeError('case3: 2')
        f1.data.append('somedata_f1')
        
        f2 = MSFile(g_root, 'case1')
        f2.load()
        f2.data.append('somedata_f2')
        f2.save()
        if f2.info.datanum != 1: raise RuntimeError('case3: 3')

        f1.save()
        if f1.info.revision != 5: raise RuntimeError('case3: 4')
        if f1.info.datanum != 2: raise RuntimeError('case3: 5')

    case1()
